package alog

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"time"
)

var (
	lfs     = map[string]*LogFile{}
	lfsLock sync.Mutex
)

const (
	bufferSize   = 4194304 // 4M Byte
	flushTimeout = 1 * time.Second
	keepDays     = 7       // TODO conf
	rotation     = "daily" // TODO conf
)

type LogFile struct {
	path string
	file *os.File
	buf  []byte
	size int64

	bufferC chan []byte
	flushC  chan bool
	reopenC chan bool
	closeC  chan bool

	// logrotate rule
	rule RotateRule
}

// 创建 LogFile
func CreateLogFile(conf *LogConf) *LogFile {
	lfsLock.Lock()
	defer lfsLock.Unlock()

	if lf, ok := lfs[conf.LogFile]; ok {
		return lf
	}

	basePath := filepath.Dir(conf.LogFile)
	if _, err := os.Stat(basePath); err != nil {
		if err = os.MkdirAll(basePath, 0755); err != nil {
			log.Fatalf("Mkdir %s err: %v", basePath, err)
		}
	}

	f, err := os.OpenFile(conf.LogFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalf("Open file %s err: %v", conf.LogFile, err)
	}

	lf := &LogFile{
		path:    conf.LogFile,
		file:    f,
		buf:     make([]byte, bufferSize),
		size:    0,
		bufferC: make(chan []byte, runtime.NumCPU()),
		flushC:  make(chan bool, 1),
		reopenC: make(chan bool, 1),
		closeC:  make(chan bool, 1),
	}

	// TODO SizeRotateRule
	// if conf.Rotation == "daily" {
	lf.rule = CreateDailyRotateRule(conf.LogFile, int(conf.KeepDays))
	// }

	lfs[conf.LogFile] = lf

	go lf.loop()

	return lf
}

// 重新打开所有 LogFile
func ReopenLogFiles() {
	lfsLock.Lock()
	defer lfsLock.Unlock()

	for _, lf := range lfs {
		lf.Reopen()
	}
}

// 关闭所有 LogFile
func CloseLogFiles() {
	lfsLock.Lock()
	defer lfsLock.Unlock()

	for _, lf := range lfs {
		lf.closeC <- true
		delete(lfs, lf.path)
	}
}

func (lf *LogFile) rotate() {
	// rename current file to backup file
	backupFileName := lf.rule.BackupFileName()
	os.Rename(lf.path, backupFileName)

	// delete outdated files
	deleteFiles := lf.rule.OutdatedFiles()
	for _, file := range deleteFiles {
		os.Remove(file)
	}

	lf.Reopen()
}

func (lf *LogFile) flush() {
	n, err := lf.file.Write(lf.buf[:lf.size])
	if err != nil {
		fmt.Printf("Write File failed, write %d, expected: %d, err: %v\n", n, len(lf.buf), err)
	}
	lf.size = 0

	// TODO SizeRotateRule
	if lf.rule.ShallRotate(0) {
		lf.rotate()
		lf.rule.MarkRotated()
	}
}

func (lf *LogFile) write(b []byte) {
	length := len(b)
	if int(lf.size)+length > len(lf.buf) {
		lf.flush()
	}

	copy(lf.buf[lf.size:], b)
	lf.size += int64(length)
}

func (lf *LogFile) loop() {
	ti := time.NewTimer(flushTimeout)

	defer func() {
		ti.Stop()
		lf.flush()
	}()

	for {
		select {
		case b := <-lf.bufferC:
			lf.write(b)
		case <-lf.flushC:
			lf.flush()
			ti = time.NewTimer(flushTimeout)
		case <-lf.reopenC:
			lf.flush()
			ti = time.NewTimer(flushTimeout)
			lf.file.Close()
			f, err := os.OpenFile(lf.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
			if err != nil {
				fmt.Printf("Open file %s err: %v\n", lf.path, err)
				return
			}
			lf.file = f
		case <-ti.C:
			lf.flush()
			ti = time.NewTimer(flushTimeout)
		case <-lf.closeC:
			return
		}
	}
}

// 向 LogFile 中写入内容
func (lf *LogFile) Write(b []byte) {
	lf.bufferC <- b
}

// 将 LogFile 中的内容刷入文件
func (lf *LogFile) Flush() {
	lf.flushC <- true
}

// 重新打开 LogFile，用于日志 rotate 使用
func (lf *LogFile) Reopen() {
	lf.reopenC <- true
}

// 停止 LogFile
func (lf *LogFile) Close() {
	lf.closeC <- true

	lfsLock.Lock()
	defer lfsLock.Unlock()

	delete(lfs, lf.path)
}
